/*
 * eds_mod.c - EDB module support
 * Clunix, cezanne@clunix.com, 2005.2
 *
 * Copyright (c) 2001-2005 by Clunix Inc. All Rights Reserved.
 */

#include "config.h"

#include "eds.h"
#include "eds_p.h"

LIST_HEAD(eds_modhead);

static ECMBOOL
load_so(eds_mod_t *mod, sr_t *sr_smod)
{
	const char	*so_name;

	if (sr_smod == NULL)
		return ECM_FALSE;
	so_name = sr_getc_str(sr_smod, "so_name");
	if (so_name) {
		mod->so_path = ecm_gen_path(ecm_default_clxdir(), "modules", so_name, NULL);
		mod->handle = ecm_dlopen(mod->so_path, ECM_FALSE);
		if (mod->handle == NULL) {
			ecm_free(mod->so_path);
			mod->so_path = NULL;
			return ECM_FALSE;
		}
	}
	return ECM_TRUE;
}

static eds_mod_t *
create_modinfo(const char *modname)
{
	eds_mod_t	*mod;

	mod = ecm_malloc(sizeof(eds_mod_t));

	mod->modname = ecm_strdup(modname);
	mod->handle = NULL;
	mod->smod_path = NULL;
	mod->so_path = NULL;
#ifdef ECM_DEBUG
	mod->dbgset = NULL;
#endif
	INIT_LIST_HEAD(&mod->list);

	return mod;
}

static sr_t *
load_smod(const char *smod_path)
{
	sr_t	*sr_smod = NULL;
        char	*buf;
        int	fd;
	ecm_size_t	filesize;

	if ((fd = ecm_open(smod_path, ECM_O_RDONLY)) == -1)
		return NULL;
	if (!ecm_filesize(fd, &filesize)) {
		ecm_close(fd);
		return NULL;
	}
	buf = (char *)ecm_malloc(sizeof(char) * filesize);
	if (ecm_read(fd, buf, &filesize))
		sr_decode(buf, &filesize, &sr_smod);
	ecm_free(buf);
	ecm_close(fd);
	return sr_smod;
}

static ECMBOOL
do_exec_initfin(ecm_ptr_t handle, const char *funcname)
{
	ECMBOOL (*funcptr)(void);

	if (handle == NULL)
		return ECM_TRUE;

	funcptr = (ECMBOOL (*)(void))ecm_dlsym(handle, funcname);
	if (funcptr == NULL)
		return ECM_TRUE;

	return funcptr();
}

ECMBOOL
eds_exec_initializer(ecm_ptr_t handle)
{
	return do_exec_initfin(handle, "initialize");
}

void
eds_exec_finalizer(ecm_ptr_t handle)
{
	do_exec_initfin(handle, "finalize");
}

eds_mod_t *
eds_find_module(const char *modname)
{
	eds_mod_t		*mod;
	struct list_head	*lp;

	list_for_each(lp, &eds_modhead) {
		mod = list_entry(lp, eds_mod_t, list);
		if (ecm_strcmp(mod->modname, modname) == 0)
			return mod;
	}
	return NULL;
}

eds_mod_t *
eds_load_module(const char *modname)
{
        sr_t		*sr_smod;
	eds_mod_t	*mod;
	char		*smodfile;

	mod = create_modinfo(modname);
	ecm_asprintf(&smodfile, "%s.smod", modname);
	mod->smod_path = ecm_gen_path(ecm_default_clxdir(), "modules", smodfile, NULL);
	ecm_free(smodfile);

	sr_smod = load_smod(mod->smod_path);
	if (sr_smod == NULL) {
		ecm_free(mod->smod_path);
		mod->smod_path = NULL;
		eds_unload_module(modname);
		return NULL;
	}

	if (!load_so(mod, sr_smod) ||
	    !eds_types_add(sr_getc(sr_smod, "types"), mod->handle) ||
	    !eds_functions_add(sr_getc(sr_smod, "funcdefs"), mod) ||
	    eds_add_UI(mod, modname, sr_getc(sr_smod, "UI")) != EDS_OK) {
		eds_unload_module(modname);
		sr_free(sr_smod);
		return NULL;
	}
	eds_add_menu(modname, sr_getc(sr_smod, "MENU"));
	eds_add_active_area(modname, sr_getc(sr_smod, "AREA"));

	sr_free(sr_smod);
	list_add_tail(&mod->list, &eds_modhead);
	return mod;
}

void
eds_unload_module(const char *modname)
{
	eds_mod_t	*mod;

	if ((mod = eds_find_module(modname)) == NULL)
		return;

	if (mod->smod_path) {
		sr_t	*sr_smod;

		sr_smod = load_smod(mod->smod_path);
		if (sr_smod) {
			eds_functions_remove(sr_getc(sr_smod, "funcdefs"));
			eds_types_remove(sr_getc(sr_smod, "types"));
			eds_del_UI(sr_getc(sr_smod, "UI"));
		}
		sr_free(sr_smod);
		if (mod->handle)
			ecm_dlclose(mod->handle);
		ecm_free(mod->smod_path);
		ecm_free(mod->so_path);
	}

	eds_del_menu(modname);

	ecm_free(mod->modname);
	list_del(&mod->list);
	ecm_free(mod);
}
